using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace WpfRcp.Controls.Gestures
{
    public class TapGestureEngine
    {
        #region Fields

        private readonly double m_MaxMilliseconds;
        private readonly double m_MinMilliseconds;
        private readonly double m_MaxMovement;
        private readonly DispatcherTimer m_Timer;

        #endregion

        #region Properties

        public TouchDevice TouchDevice { get; set; }
        
        public Point StartPoint { get; set; }
        
        public Point EndPoint { get; set; }
        
        public Vector TranslationDelta
        {
            get { return EndPoint - StartPoint; }
        }

        public DateTime TouchDownTime { get; private set; }

        public DateTime TouchUpTime { get; private set; }

        public TimeSpan TimeDelta
        {
            get
            {
                if (IsStarted && IsCompleted)
                    return TouchUpTime - TouchDownTime;

                if (IsStarted)
                    return DateTime.Now - TouchDownTime;

                return TimeSpan.FromMilliseconds(0);
            }
        }

        public bool IsStarted { get; set; }
        
        public bool IsCompleted { get; set; }
        
        public bool IsAborted { get; set; }

        #endregion

        #region Events

        public event EventHandler GestureStarted;

        private void OnGestureStarted()
        {
            if (GestureStarted != null)
            {
                GestureStarted(this, EventArgs.Empty);
            }
        }

        public event EventHandler GestureCompleted;

        private void OnGestureCompleted()
        {
            if (GestureCompleted != null)
            {
                GestureCompleted(this, EventArgs.Empty);
            }
        }

        public event EventHandler GestureAborted;

        private void OnGestureAborted()
        {
            if (GestureAborted != null)
            {
                GestureAborted(this, EventArgs.Empty);
            }
        }

        #endregion

        #region Constructors

        public TapGestureEngine(double minMilliseconds, double maxMilliseconds, double maxMovement)
        {
            IsStarted = false;
            IsCompleted = false;
            IsAborted = false;

            m_MaxMilliseconds = maxMilliseconds;
            m_MinMilliseconds = minMilliseconds;
            m_MaxMovement = maxMovement;

            m_Timer = new DispatcherTimer();
            m_Timer.Interval = TimeSpan.FromMilliseconds(m_MaxMilliseconds);
            m_Timer.Tick += (s, e) =>
            {
                AbortGesture();
            };
        }

        #endregion

        #region Public methods

        public void TrackTouchDown(DateTime time, Point position)
        {
            if (IsCompleted || IsAborted)
                return;

            TouchDownTime = time;
            StartPoint = position;
            IsStarted = true;

            OnGestureStarted();

            m_Timer.Start();
        }

        public void TrackTouchMove(Point position)
        {
            if (IsCompleted || IsAborted)
                return;

            Vector delta = position - StartPoint;
            if (delta.Length > m_MaxMovement)
            {
                AbortGesture();
            }
        }

        public void TrackTouchUp(DateTime time, Point position)
        {
            if (IsCompleted || IsAborted)
                return;

            TouchUpTime = time;
            EndPoint = position;

            ProcessStatus();
        }

        public void AbortGesture()
        {
            m_Timer.Stop();
            IsAborted = true;
            ProcessStatus();
        }

        #endregion

        #region Private Methods

        private void ProcessStatus()
        {
            if (IsAborted)
            {
                OnGestureAborted();
                return;
            }

            double milliseconds = TimeDelta.TotalMilliseconds;

            if (milliseconds < m_MinMilliseconds ||
                milliseconds > m_MaxMilliseconds)
            {
                IsAborted = true;
                OnGestureAborted();
                return;
            }

            if (TranslationDelta.Length > m_MaxMovement)
            {
                IsAborted = true;
                OnGestureAborted();
                return;
            }

            IsCompleted = true;
            OnGestureCompleted();
        }

        #endregion
    }
}